package com.skywolf.chem.match.handle;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.AlipayTradeCloseModel;
import com.alipay.api.domain.AlipayTradeWapPayModel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.ijpay.alipay.AliPayApi;
import com.ijpay.alipay.AliPayApiConfig;
import com.ijpay.alipay.AliPayApiConfigKit;
import com.skywolf.chem.common.config.data.DataSource;
import com.skywolf.chem.common.config.data.DataSourceNames;
import com.skywolf.chem.common.sysconfig.exception.BizException;
import com.skywolf.chem.match.common.constants.MatchConstants;
import com.skywolf.chem.match.common.enums.MatchApplyRecordStatusEnum;
import com.skywolf.chem.match.common.enums.MatchOrderStatusEnum;
import com.skywolf.chem.match.common.enums.MatchPayTypeEnum;
import com.skywolf.chem.match.common.enums.RefundStatusEnum;
import com.skywolf.chem.match.entity.ApplyUserRecord;
import com.skywolf.chem.match.entity.GroupInfo;
import com.skywolf.chem.match.entity.Order;
import com.skywolf.chem.match.entity.RefundOrder;
import com.skywolf.chem.match.mapper.OrderMapper;
import com.skywolf.chem.match.params.vo.OrderPayVo;
import com.skywolf.chem.match.pay.alibaba.config.AliPayNotifyParam;
import com.skywolf.chem.match.pay.alibaba.config.AliPayProperties;
import com.skywolf.chem.match.pay.wx.config.WxPayProperties;
import com.skywolf.chem.match.service.IApplyUserRecordService;
import com.skywolf.chem.match.service.IGroupInfoService;
import com.skywolf.chem.match.service.IOrderService;
import com.skywolf.chem.match.service.IRefundOrderService;
import com.skywolf.chem.match.service.impl.ApplyUserRecordServiceImpl;
import com.skywolf.chem.match.utils.*;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 订单支付处理器
 *
 * @Author sj
 * Date on 2021/10/14  9:46
 * @Version 1.0
 */
@Component
@AllArgsConstructor
@Slf4j
public class OrderPayHandle {

    private final OrderMapper orderMapper;

    private final AliPayApiConfig aliPayApiConfig;

    private final AliPayProperties aliPayProperties;

    private final WxPayService wxPayService;

    private final WxPayProperties wxPayProperties;

    private final RedisUtils redisUtils;

    private final IRefundOrderService refundOrderService;

    private final RedisTemplate<String, Object> redisTemplate;

    private final IApplyUserRecordService applyUserRecordService;

    private final IGroupInfoService groupInfoService;

    /**
     * 支付宝支付初始化
     */
    public void aliPayConfig() {
        AliPayApiConfigKit.setThreadLocalAliPayApiConfig(aliPayApiConfig);
    }

    /**
     * 支付宝支付
     *
     * @param order
     * @return
     */
    @DataSource(DataSourceNames.TWO)
    public OrderPayVo zfbPay(Order order, RLock lock) throws Exception {
        try {
            aliPayConfig();
            if (MatchPayTypeEnum.ALIPAY.getType().equals(order.getPayType())
                    && StrUtil.isNotBlank(order.getOutTradeNo())) {
                //支付宝关单
                try {
                    // 如果已经有outTradeNo了就先进行关单
                    aliOrderClose(order);
                } catch (AlipayApiException e) {
                    log.error(e.getMessage(), e);
                }
            }
            AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
            model.setBody("报名支付-订单编号" + order.getOrderSn());
            model.setSubject("报名费用支付");
            BigDecimal totalAmount = new BigDecimal(order.getTotalAmount()).divide(new BigDecimal(100), 2, BigDecimal.ROUND_HALF_UP);
            //订单价格
            model.setTotalAmount(totalAmount.toString());
            model.setQuitUrl(aliPayProperties.getReturnUrl());
            // 用户id前补零保证五位，对超出五位的保留后五位
            String userIdFilledZero = String.format("%05d", order.getMatchUserId());
            String fiveDigitsUserId = userIdFilledZero.substring(userIdFilledZero.length() - 5);
            // 在前面加上wxo（weixin order）等前缀是为了人工可以快速分辨订单号是下单还是退款、来自哪家支付机构等
            // 将时间戳+3位随机数+五位id组成商户订单号
            String outTradeNo = "ali_" + System.currentTimeMillis() + RandomUtil.randomNumbers(3) + fiveDigitsUserId;
            model.setOutTradeNo(outTradeNo);
            model.setProductCode("QUICK_WAP_PAY");
            String from = AliPayApi.wapPayStr(model, aliPayProperties.getReturnUrl(), aliPayProperties.getNotifyUrl());
            // 更新订单状态
            order.setPayType(MatchPayTypeEnum.ALIPAY.getType());
            order.setOutTradeNo(outTradeNo);
            orderMapper.updateById(order);
            OrderPayVo orderPayVo = new OrderPayVo();
            orderPayVo.setPayType(2);
            orderPayVo.setAliResult(from);
            return orderPayVo;
        } catch (Exception e) {
            lock.unlock();
            log.error(e.getMessage());
            throw new BizException("支付宝统一下单失败！");
        }
    }

    /**
     * 支付宝关单
     *
     * @param order
     */
    public void aliOrderClose(Order order) throws AlipayApiException {
        aliPayConfig();
        AlipayTradeCloseModel closeModel = new AlipayTradeCloseModel();
        closeModel.setOutTradeNo(order.getOutTradeNo());
        AliPayApi.tradeCloseToResponse(closeModel);
    }

    /**
     * 微信支付
     *
     * @param order
     * @return
     */
    @DataSource(DataSourceNames.TWO)
    public OrderPayVo wxJsapiPay(Order order, RLock lock, String source) {
        Long payAmount = order.getTotalAmount();
        // 如果已经有outTradeNo了就先进行关单
        if (MatchPayTypeEnum.WX_JSAPI.getType().equals(order.getPayType()) && StrUtil.isNotBlank(order.getOutTradeNo())) {
            try {
                wxPayService.closeOrderV3(order.getOutTradeNo());
            } catch (WxPayException e) {
                log.error(e.getMessage(), e);
                throw new BizException("微信关单异常");
            }
        }
        // 用户id前补零保证五位，对超出五位的保留后五位
        String userIdFilledZero = String.format("%05d", order.getMatchUserId());
        String fiveDigitsUserId = userIdFilledZero.substring(userIdFilledZero.length() - 5);
        // 在前面加上wxo（weixin order）等前缀是为了人工可以快速分辨订单号是下单还是退款、来自哪家支付机构等
        // 将时间戳+3位随机数+五位id组成商户订单号
        String outTradeNo = "wxo_" + System.currentTimeMillis() + RandomUtil.randomNumbers(3) + fiveDigitsUserId;
        WxPayUnifiedOrderV3Request.SceneInfo sceneInfo = new WxPayUnifiedOrderV3Request.SceneInfo();
        String ip = IpUtil.getIpAddr(RequestHolder.getRequest());
        sceneInfo.setH5Info(new WxPayUnifiedOrderV3Request.H5Info().setType(source));
        sceneInfo.setPayerClientIp(ip);
        WxPayUnifiedOrderV3Request wxRequest = new WxPayUnifiedOrderV3Request()
                .setOutTradeNo(outTradeNo)
                .setAppid(wxPayProperties.getAppId())
                .setNotifyUrl(wxPayProperties.getPayNotifyUrl())
                .setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(Math.toIntExact(payAmount)))
                .setDescription("报名支付-订单编号" + order.getOrderSn())
                .setSceneInfo(sceneInfo);
        String jsapiResult = "";
        try {
            jsapiResult = wxPayService.createOrderV3(TradeTypeEnum.H5, wxRequest);
            // 更新订单状态
            order.setPayType(MatchPayTypeEnum.WX_JSAPI.getType());
            order.setOutTradeNo(outTradeNo);
            orderMapper.updateById(order);
        } catch (WxPayException e) {
            lock.unlock();
            throw new BizException("微信统一下单异常");
        }
        return new OrderPayVo().setPayType(MatchPayTypeEnum.WX_JSAPI.getType()).setWxResult(jsapiResult);
    }

    /**
     * 处理支付宝回调函数
     *
     * @param params
     */
    @DataSource(DataSourceNames.TWO)
    public void handleAliPayOrderNotify(Map<String, String> params) {
        AliPayNotifyParam param = FastJsonUtils.convertJSONToObject(FastJsonUtils.collectToString(params), AliPayNotifyParam.class);
        //订单关闭回调通知
        if (param.getTradeStatus().equals("TRADE_CLOSED")) {
            return;
        }
        if (param.getTradeStatus().equals("TRADE_SUCCESS")
                || param.getTradeStatus().equals("TRADE_FINISHED")) {
            if (null != param.getRefundFee()) {
                //退款通知
                return;
            }
            if (redisUtils.hasKey(MatchConstants.REFUND_NOTIFY_PREFIX)) {
                return;
            }
            Order order = orderMapper.selectOne(new LambdaQueryWrapper<Order>().eq(Order::getOutTradeNo, param.getOutTradeNo()));
            if (StringUtils.isEmpty(order)) {
                throw new BizException("订单不存在");
            }
            order.setStatus(MatchOrderStatusEnum.PAYED.getStatus());
            order.setTransactionId(param.getTradeNo());
            order.setPayAmount(param.getTotalAmount().multiply(new BigDecimal(100)).longValue());
            order.setPayTime(LocalDateTime.now());
            orderMapper.updateById(order);
            //更新报名人状态为报名状态
            SpringContextHolder.getBean(IApplyUserRecordService.class).update(new LambdaUpdateWrapper<ApplyUserRecord>()
                    .set(ApplyUserRecord::getApplyStatus, MatchApplyRecordStatusEnum.APPLY_NORMAL_STATUS.getStatus())
                    .eq(ApplyUserRecord::getMatchOrderId, order.getId()));
            //支付成功扣减库存
            SpringContextHolder.getBean(MatchGroupStockHandle.class).deductStock(order.getOrderSn());
            //设置防重复回调key 5分钟内防止重复回调
            redisUtils.set(MatchConstants.ORDER_NOTIFY_PREFIX + order.getOrderSn(), 1, 5);
        }
    }

    /**
     * 处理微信支付退款通知
     *
     * @param signatureHeader
     * @param notifyData
     * @throws WxPayException
     */
    @Transactional(rollbackFor = Exception.class)
    @DataSource(DataSourceNames.TWO)
    public void handleWxPayRefundNotify(SignatureHeader signatureHeader, String notifyData) throws WxPayException {
        // 解密退款通知内容
        final WxPayRefundNotifyV3Result.DecryptNotifyResult result =
                this.wxPayService.parseRefundNotifyV3Result(notifyData, signatureHeader).getResult();
        log.debug("退款通知解密成功：[{}]", result.toString());
        //防止重复回调
        if (redisUtils.hasKey(MatchConstants.REFUND_NOTIFY_PREFIX + result.getOutRefundNo())) {
            return;
        }
        //查询退款子订单
        RefundOrder refundOrder = refundOrderService.getOne(new LambdaQueryWrapper<RefundOrder>().eq(RefundOrder::getOrderSn, result.getOutRefundNo()));

        // 退款成功处理
        if (WxPayConstants.RefundStatus.SUCCESS.equals(result.getRefundStatus())) {
            SpringContextHolder.getBean(IRefundOrderService.class).update(new LambdaUpdateWrapper<RefundOrder>()
                    .set(RefundOrder::getRefundSuccessTime, result.getSuccessTime())
                    .set(RefundOrder::getOutRefundNo, result.getOutRefundNo())
                    .set(RefundOrder::getRefundId, result.getRefundId())
                    .set(RefundOrder::getRefundStatus, RefundStatusEnum.SUCCESS.getStatus())
                    .eq(RefundOrder::getOrderSn, result.getOutRefundNo()));
            //修改报名人状态
            applyUserRecordService.update(new LambdaUpdateWrapper<ApplyUserRecord>()
                    .set(ApplyUserRecord::getApplyStatus, MatchApplyRecordStatusEnum.APPLY_REFUND.getStatus()).eq(ApplyUserRecord::getId, refundOrder.getApplyUserId()));
            Order order = orderMapper.selectById(refundOrder.getParentOrderId());
            if (!StringUtils.isEmpty(order)) {
                //给指定活动指定组别更新库存
                groupInfoService.update(new LambdaUpdateWrapper<GroupInfo>()
                        .eq(GroupInfo::getId, order.getMatchGroupId())
                        // 扣减库存
                        .setSql("stock = stock + " + 1));
                GroupInfo groupInfo = groupInfoService.getById(order.getMatchGroupId());
                // 写->缓存
                redisTemplate.opsForValue().set(MatchConstants.LOCKED_STOCK_PREFIX + order.getMatchGroupId(), String.valueOf(groupInfo.getStock()));
            }
            //更新订单报名人数
            SpringContextHolder.getBean(IOrderService.class).update(new LambdaUpdateWrapper<Order>().eq(Order::getId, order.getId()).setSql("total_quantity=total_quantity-1"));
            //设置防重复回调key 5分钟内防止重复回调
            redisUtils.set(MatchConstants.REFUND_NOTIFY_PREFIX + result.getOutRefundNo(), 1, 5);
        } else {
            refundOrderService.update(new LambdaUpdateWrapper<RefundOrder>()
                    .set(RefundOrder::getRefundSuccessTime, result.getSuccessTime())
                    .set(RefundOrder::getOutRefundNo, result.getOutRefundNo())
                    .set(RefundOrder::getRefundId, result.getRefundId())
                    .set(RefundOrder::getRefundStatus, RefundStatusEnum.getByName(result.getRefundStatus()).getStatus())
                    .eq(RefundOrder::getOrderSn, result.getOutRefundNo()));
            //修改报名人状态
            applyUserRecordService.update(new LambdaUpdateWrapper<ApplyUserRecord>()
                    .set(ApplyUserRecord::getApplyStatus, MatchApplyRecordStatusEnum.APPLY_REFUND_ERR.getStatus()).eq(ApplyUserRecord::getId, refundOrder.getApplyUserId()));
        }

    }

    /**
     * 处理微信支付成功通知
     *
     * @param signatureHeader
     * @param notifyData
     * @throws WxPayException
     */
    @Transactional(rollbackFor = Exception.class)
    @DataSource(DataSourceNames.TWO)
    public void handleWxPayOrderNotify(SignatureHeader signatureHeader, String notifyData) throws WxPayException {
        log.info("开始处理支付结果通知");

        // 解密支付通知内容
        final WxPayOrderNotifyV3Result.DecryptNotifyResult result =
                this.wxPayService.parseOrderNotifyV3Result(notifyData, signatureHeader).getResult();
        log.debug("支付通知解密成功：[{}]", result.toString());
        //重复回调
        if (redisUtils.hasKey(MatchConstants.ORDER_NOTIFY_PREFIX + result.getOutTradeNo())) {
            return;
        }
        // 根据商户订单号查询订单
        QueryWrapper<Order> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(Order::getOutTradeNo, result.getOutTradeNo());
        Order orderDO = orderMapper.selectOne(wrapper);
        // 支付成功处理
        if (WxPayConstants.WxpayTradeStatus.SUCCESS.equals(result.getTradeState())) {
            orderDO.setStatus(MatchOrderStatusEnum.PAYED.getStatus());
            orderDO.setTransactionId(result.getTransactionId());
            orderDO.setPayAmount(orderDO.getTotalAmount());
            orderDO.setPayTime(LocalDateTime.now());
            orderMapper.updateById(orderDO);
            //更新报名人状态为报名状态
            applyUserRecordService.update(new LambdaUpdateWrapper<ApplyUserRecord>()
                    .set(ApplyUserRecord::getApplyStatus, MatchApplyRecordStatusEnum.APPLY_NORMAL_STATUS.getStatus())
                    .eq(ApplyUserRecord::getMatchOrderId, orderDO.getId()));
            //支付成功扣减库存
            SpringContextHolder.getBean(MatchGroupStockHandle.class).deductStock(orderDO.getOrderSn());
            //设置防重复回调key 5分钟内防止重复回调
            redisUtils.set(MatchConstants.ORDER_NOTIFY_PREFIX + orderDO.getOrderSn(), 1, 5);
        }
        log.info("账单更新成功");
    }
}
